package com.usc.csci588.chaltrojan;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;

import com.google.android.maps.*;
import com.usc.csci588.chaltrojan.R;
import com.usc.csci588.chaltrojan.location.map.CTItemizedOverlay;
import com.usc.csci588.chaltrojan.location.map.RouteOverlay;
import com.usc.csci588.chaltrojan.location.map.route.NavigationDataSet;
import com.usc.csci588.chaltrojan.location.map.route.NavigationSaxHandler;
import com.usc.csci588.chaltrojan.location.map.route.Road;
import com.usc.csci588.chaltrojan.location.map.route.RoadProvider;

import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.StrictMode;
import android.speech.RecognizerIntent;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.Toast;

public class MainActivity extends MapActivity {

	MapView mv = null;
	MapController mc = null;
	List<Overlay> mapOverlays = null;
	
	private Handler handler;
	
	LinearLayout searchLayout = null;
	EditText searchFor = null;
	
	private Boolean showResults = null;
	Location myInitialLocation = null;
	private Road mRoad = null;
	
	static final int HIGH_ZOOM = 17;
	static final int MED_HIGH_ZOOM = 15;
	static final int MED_ZOOM = 12;
	static final int LOW_ZOOM = 7;
    private static final int RECOGNIZER_EXAMPLE = 1001;
    
    public Boolean getShowResults() {
		return showResults;
	}

	public void setShowResults(Boolean showResults) {
		this.showResults = showResults;
	}

	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        handler = new Handler();
        setContentView(R.layout.map_view_1);
        
        setShowResults(false);
        
        mv = (MapView) this.findViewById(R.id.map1);
        mv.setBuiltInZoomControls(true);
        mapOverlays = mv.getOverlays();
        searchFor = (EditText) findViewById(R.id.searchPlace);

        mv.setSatellite(false);
        mv.setClickable(true);
        //mv.setst
        
        ImageButton searchBtn = (ImageButton) findViewById(R.id.searchBtn);
        
        searchBtn.setOnClickListener(generateOnClickListener());
        
        searchLayout = (LinearLayout)findViewById(R.id.search);
        
        searchLayout.setPadding(0, 1, 0, 0);
        searchLayout.setBackgroundColor(Color.LTGRAY);
        //searchLayout.invalidate();
        
        mc = mv.getController();
		mc.setZoom(MED_ZOOM);
        
        LocationManager lm = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
        LocationListener ll = new LocationListener() {

			/**
			 * Most useful method - called whenever GPS changes location
			 * The center point is set to the new location whenever that happens
			 */
			@Override
			public void onLocationChanged(Location point) {
				//Log.d("maps","getShowResults() = "+getShowResults());
				if (getShowResults()) {
					return;
				}

				Log.d("maps","location changed - It is now "+
						point.getLatitude()+ " "+point.getLongitude());
				mapOverlays.clear();
				GeoPoint p =  createGeoPoint(point.getLatitude() , point.getLongitude() );
				if (!getShowResults()) {
					mc.animateTo(p);
					mc.setZoom(MED_HIGH_ZOOM);
				}
				setupOverlay(mv, p);
				
				/**
				 * This is for testing if we can show two points simultaneously
				 * //TODO - Remove after testing 
				 */

				///*
				List<GeoPoint> otherPoints = getOtherLocations(p);
				setupOverlay(mv, otherPoints);
		        //*/
				
				mv.invalidate();
				
			}

			@Override
			public void onProviderDisabled(String provider) {
				// TODO Auto-generated method stub
				
			}

			@Override
			public void onProviderEnabled(String provider) {
				// TODO Auto-generated method stub
				
			}

			@Override
			public void onStatusChanged(String provider, int status,
					Bundle extras) {
				// TODO Auto-generated method stub
			}
        	
        };
        
        /**
         * http://developer.android.com/guide/topics/location/strategies.html
         */
        lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 50, ll);
       
        myInitialLocation = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
        

        if (myInitialLocation == null) {
        	myInitialLocation = new Location("");
        	myInitialLocation.setLatitude(34.0);
        	myInitialLocation.setLongitude(-118.0);
        }
        
       GeoPoint p = createGeoPoint(myInitialLocation.getLatitude() , myInitialLocation.getLongitude() ); 
       mapOverlays.clear();
		setupOverlay(mv, p);       

       /**
        * This is for testing if we can show two points simultaneously
        * //TODO - Remove after testing 
        */
       
       /*
			GeoPoint p2 = new GeoPoint(
            (int) (intialLocation.getLatitude() * 1.00001 * 1e6), 
            (int) (intialLocation.getLongitude() * 1.00031 * 1e6) );
       		setupOverlay(mv, p2);

        //*/
       
		Log.d("maps","location p is Lat: "+
				myInitialLocation.toString() );
        
        mc.animateTo(p);        
        mv.invalidate();
        
        ImageButton voiceButton = (ImageButton) findViewById(R.id.voiceBtn);
        voiceButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                // RecognizerIntent prompts for speech and returns text
                try {
                Intent intent =
                   new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
                intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                        RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
                intent.putExtra(RecognizerIntent.EXTRA_PROMPT,
                "Where to?");
                startActivityForResult(intent, RECOGNIZER_EXAMPLE);
                } catch (ActivityNotFoundException anfe) {
                	
                }
            }
        });
  

   }
    
    private GeoPoint createGeoPoint(double latitude, double longitude){
    	return new GeoPoint( (int) (latitude * 1E6 ),
				(int) (longitude * 1E6) );
    }
    
    /**
     * This is for search button, we click on it and we find out the 
     * route that we ought to take
     * @return
     */
    private View.OnClickListener generateOnClickListener(){
    	return new View.OnClickListener() {
    		 public void onClick(View v) {
                 // Perform action on click
    		        Geocoder geoCoder = new Geocoder(v.getContext());
					try {
						List<Address> addresses =  geoCoder.getFromLocationName(searchFor.getText().toString(),2);
	    		        Log.d("maps","Found results = "+addresses.size());
		                List<GeoPoint> geopoints = new ArrayList<GeoPoint>();
	    		        setShowResults(false);
						if(addresses.size() > 0)
		                {
					        mv.setSatellite(false);
							for (int i=0; i<addresses.size(); i++) {
								geopoints.add(createGeoPoint(addresses.get(i).getLatitude(), addresses.get(0).getLongitude() ));
			    		        Log.d("maps","Result ["+i+"] = "+addresses.get(i).getLatitude()+ "  " + addresses.get(0).getLongitude());
							
								//TODO - Comment below part - this is only for testing
								//i+= addresses.size();
							}

							//mapOverlays.clear();
		                     
							GeoPoint p = createGeoPoint(myInitialLocation.getLatitude() , myInitialLocation.getLongitude() ); 
						   	mapOverlays.clear();
							setupOverlay(mv, p);       

							Drawable drawable = getResources().getDrawable(R.drawable.map_marker_search);
							setupOverlay(mv, geopoints, drawable);
							mc.animateTo(geopoints.get(0));
							mc.setZoom(MED_ZOOM);
							
							Toast.makeText(getApplicationContext(), "Total overlays = "+ mapOverlays.size() , Toast.LENGTH_LONG).show();


							mv.invalidate();
							//searchFor.setText("");
							
							double fromLat = myInitialLocation.getLatitude();
							double fromLon = myInitialLocation.getLongitude();
							
							double toLat = addresses.get(0).getLatitude();
							double toLon = addresses.get(0).getLongitude();
							
							Toast.makeText(getApplicationContext(), "Tap on a location to go there!!", Toast.LENGTH_LONG).show();
							
							displayRoute(fromLat, fromLon, toLat, toLon);
		                }
		                else
		                {
		                        AlertDialog.Builder adb = new AlertDialog.Builder(MainActivity.this);
		                        adb.setTitle("Chal, Trojan!");
		                        adb.setMessage("Unable to find the place");
		                        adb.setPositiveButton("Close",null);
		                        adb.show();
		                }
		                
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					/*
					catch (SAXException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (ParserConfigurationException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					*/ catch (SAXException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (ParserConfigurationException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

             }

    	};
    }
    
    @Override
    protected void onActivityResult(int requestCode,
                                    int resultCode, Intent data) {
    	//use a switch statement for more than one request code check
    	if (requestCode==RECOGNIZER_EXAMPLE && resultCode==RESULT_OK) {
    		// returned data is a list of matches to the speech input
    		ArrayList<String> result =
    				data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
    		//display on screen
    		searchFor.setText(result.get(0).toString());
    	}
    	super.onActivityResult(requestCode, resultCode, data);
    }

    
    /**
     * All overlay elements on a map are held by the MapView, so when you want to add some, 
     * you have to get a list from the getOverlays() method.
     * Then instantiate the Drawable used for the map marker, which was saved 
     * in the res/drawable/ directory. 
     * @param mapView
     */

    private void setupOverlay(MapView mapView, OverlayItem overlayitem){
    	Drawable drawable = this.getResources().getDrawable(R.drawable.map_marker);
    	CTItemizedOverlay itemizedoverlay = new CTItemizedOverlay(drawable, this);
    	itemizedoverlay.addOverlay(overlayitem);

    	mapOverlays.add(itemizedoverlay);
    }
    
    private void setupOverlay(MapView mapView, GeoPoint p){
        OverlayItem overlayitem = new OverlayItem(p, "Here, you are!!", "I'm here!");
        setupOverlay(mapView, overlayitem);
    } 
    
    private void setupOverlay(MapView mapView, OverlayItem... overlayitems){
    	Drawable drawable = this.getResources().getDrawable(R.drawable.map_marker);
    	setupOverlay(mapView, drawable, overlayitems);
    	
    	/*
    	CTItemizedOverlay itemizedoverlay = new CTItemizedOverlay(drawable, this);

    	for (OverlayItem overlayitem : overlayitems) {
        	itemizedoverlay.addOverlay(overlayitem);
        	mapOverlays.add(itemizedoverlay);    		
    	}
    	*/
    }
    
    private void setupOverlay(MapView mapView, Drawable d, OverlayItem... overlayitems){
    	CTItemizedOverlay itemizedoverlay = new CTItemizedOverlay(d, this);

    	for (OverlayItem overlayitem : overlayitems) {
        	itemizedoverlay.addOverlay(overlayitem);
        	mapOverlays.add(itemizedoverlay);    		
    	}
    }    
    
    private void setupOverlay(MapView mapView, GeoPoint... points){
    	OverlayItem overlayitems[] = new OverlayItem[points.length];
    	for (int i=0; i< points.length; i++) {
    		overlayitems[i] = new OverlayItem(points[i], null, null);
    	}
        setupOverlay(mapView, overlayitems);
    }
    
    private void setupOverlay(MapView mapView, List<GeoPoint> points){
    	OverlayItem overlayitems[] = new OverlayItem[points.size()];
    	for (int i=0; i< points.size(); i++) {
    		overlayitems[i] = new OverlayItem(points.get(i), null, null);
    	}
        setupOverlay(mapView, overlayitems);
    }    
   
    private void setupOverlay(MapView mapView, List<GeoPoint> points, Drawable d){
    	OverlayItem overlayitems[] = new OverlayItem[points.size()];
    	for (int i=0; i< points.size(); i++) {
    		overlayitems[i] = new OverlayItem(points.get(i), null, null);
    	}
        setupOverlay(mapView, d, overlayitems);
    }    
     
    
    /**
     * This function will get points of friends to be obtained from server
     * @param p - this person's pointer
     */
    private List<GeoPoint> getOtherLocations(GeoPoint p){
    	List<GeoPoint> points = new ArrayList<GeoPoint>();
    	/**
    	 * current dummy point generator
    	 */
    	
    	points.add(new GeoPoint( (int) (p.getLatitudeE6()*1.00011), (int) (p.getLongitudeE6()*1.00011)) );
    	points.add(new GeoPoint( (int) (p.getLatitudeE6()*1.00031), (int) (p.getLongitudeE6()*1.0002)) );
    	
    	return points;
    }
    

    private InputStream getConnection(String url) {
    	InputStream is = null;
    	try {
            final URL aUrl = new URL(url);
            final URLConnection conn = aUrl.openConnection();
            conn.connect();
    		is = aUrl.openStream();
    	} catch (MalformedURLException e) {
    		e.printStackTrace();
    	} catch (IOException e) {
    		e.printStackTrace();
    	}
    	return is;
    }

    @Override
    protected boolean isRouteDisplayed() {
    	return false;
    }

	@TargetApi(9)
	private void displayRoute(final double fromLat, final double fromLon,
			final double toLat, final double toLon) throws IOException, SAXException, ParserConfigurationException {
		
		String urlString = RoadProvider.getUrl(fromLat, fromLon, toLat, toLon);

		final URL url = new URL(urlString.toString());

        // create the factory
        SAXParserFactory factory = SAXParserFactory.newInstance();
        // create a parser
        SAXParser parser = factory.newSAXParser();
        // create the reader (scanner)
        XMLReader xmlreader = parser.getXMLReader();
        // instantiate our handler
        NavigationSaxHandler navSaxHandler = new NavigationSaxHandler();
        // assign our handler
        xmlreader.setContentHandler(navSaxHandler);
        // get our data via the url class
        
		Log.d("maps","urlString is "+urlString);
		
		/*
		Runnable r  = new Runnable(){
			   @Override
			   public void run() {
				   String kmlString;
				try {
					kmlString = convertStreamToString(url.openStream());
					Log.d("maps","stream is "+kmlString); 
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

			   }
		};
		handler.post(r);
		*/
		
		StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); 
		StrictMode.setThreadPolicy(policy); 
		
		/*
		new Thread() {
			@Override
			public void run() {
				double fromLat = 49.85, fromLon = 24.016667, toLat = 50.45, toLon = 30.523333;
				String url = RoadProvider.getUrl(fromLat, fromLon, toLat, toLon);
				InputStream is = getConnection(url);
				
				//Testing xml
								
				//String xml = convertStreamToString(is);
				//Log.d("maps", xml);
				//System.err.println(xml);
				
				mRoad = RoadProvider.getRoute(is);
				if (mRoad == null) {
					Log.d("maps", "mRoad == null");
				}
				handler.sendEmptyMessage(0);
			}
		}.start();
		
		//*/

		
		String kmlString = convertStreamToString(url.openStream());
		Log.d("maps","stream is "+kmlString); 
        
        InputSource is = new InputSource(url.openStream());
        xmlreader.parse(is);

        
		/*

        
        // perform the synchronous parse           
        // get the results - should be a fully populated RSSFeed instance, or null on error
        
        NavigationDataSet ds = navSaxHandler.getParsedData();
        drawPath(ds, Color.parseColor("#add331"), mv );

		//*/
        
		/*
		new Thread() {
			@Override
			public void run() {
				double fromLat = 49.85, fromLon = 24.016667, toLat = 50.45, toLon = 30.523333;
				String url = RoadProvider.getUrl(fromLat, fromLon, toLat, toLon);
				InputStream is = getConnection(url);
				
				//Testing xml
								
				//String xml = convertStreamToString(is);
				//Log.d("maps", xml);
				//System.err.println(xml);
				
				mRoad = RoadProvider.getRoute(is);
				if (mRoad == null) {
					Log.d("maps", "mRoad == null");
				}
				mHandler.sendEmptyMessage(0);
			}
		}.start();
		//*/
	}

	
	/*
    Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
	        	AlertDialog.Builder adb = new AlertDialog.Builder(MainActivity.this);
	        	adb.setTitle("Chal, Trojan!");
	        	adb.setMessage(mRoad.mName + " " + mRoad.mDescription);
	        	adb.setPositiveButton("Close",null);
	        	adb.show();

                RoadRouteOverlay mapOverlay = new RoadRouteOverlay(mRoad, mv);
                List<Overlay> listOfOverlays = mv.getOverlays();
                listOfOverlays.clear();
                listOfOverlays.add(mapOverlay);
                mv.invalidate();
        };
    };
    //*/

    static public String convertStreamToString(InputStream is) { 
    	   try {
    	        return new java.util.Scanner(is).useDelimiter("\\A").next();
    	    } catch (java.util.NoSuchElementException e) {
    	        return "";
    	    }
    }

    /**
     * Does the actual drawing of the route, based on the geo points provided in the nav set
     *
     * @param navSet     Navigation set bean that holds the route information, incl. geo pos
     * @param color      Color in which to draw the lines
     * @param mMapView01 Map view to draw onto
     */
    public void drawPath(NavigationDataSet navSet, int color, MapView mMapView01) {

        //Log.d(myapp.APP, "map color before: " + color);        

        // color correction for dining, make it darker
        if (color == Color.parseColor("#add331")) color = Color.parseColor("#6C8715");
        //Log.d(myapp.APP, "map color after: " + color);

        Collection overlaysToAddAgain = new ArrayList();
        for (Iterator<Overlay> iter = mMapView01.getOverlays().iterator(); iter.hasNext();) {
            Object o = iter.next();
            //Log.d(myapp.APP, "overlay type: " + o.getClass().getName());
            if (!RouteOverlay.class.getName().equals(o.getClass().getName())) {
                // mMapView01.getOverlays().remove(o);
                overlaysToAddAgain.add(o);
            }
        }
        mMapView01.getOverlays().clear();
        mMapView01.getOverlays().addAll(overlaysToAddAgain);

        String path = navSet.getRoutePlacemark().getCoordinates();
        //Log.d(myapp.APP, "path=" + path);
        if (path != null && path.trim().length() > 0) {
            String[] pairs = path.trim().split(" ");

            //Log.d(myapp.APP, "pairs.length=" + pairs.length);

            String[] lngLat = pairs[0].split(","); // lngLat[0]=longitude lngLat[1]=latitude lngLat[2]=height

            //Log.d(myapp.APP, "lnglat =" + lngLat + ", length: " + lngLat.length);

            if (lngLat.length<3) lngLat = pairs[1].split(","); // if first pair is not transferred completely, take seconds pair //TODO 

            try {
                GeoPoint startGP = new GeoPoint((int) (Double.parseDouble(lngLat[1]) * 1E6), (int) (Double.parseDouble(lngLat[0]) * 1E6));
                mMapView01.getOverlays().add(new RouteOverlay(startGP, startGP, 1));
                GeoPoint gp1;
                GeoPoint gp2 = startGP;

                for (int i = 1; i < pairs.length; i++) // the last one would be crash
                {
                    lngLat = pairs[i].split(",");

                    gp1 = gp2;

                    if (lngLat.length >= 2 && gp1.getLatitudeE6() > 0 && gp1.getLongitudeE6() > 0
                            && gp2.getLatitudeE6() > 0 && gp2.getLongitudeE6() > 0) {

                        // for GeoPoint, first:latitude, second:longitude
                        gp2 = new GeoPoint((int) (Double.parseDouble(lngLat[1]) * 1E6), (int) (Double.parseDouble(lngLat[0]) * 1E6));

                        if (gp2.getLatitudeE6() != 22200000) { 
                            mMapView01.getOverlays().add(new RouteOverlay(gp1, gp2, 2, color));
                            //Log.d(myapp.APP, "draw:" + gp1.getLatitudeE6() + "/" + gp1.getLongitudeE6() + " TO " + gp2.getLatitudeE6() + "/" + gp2.getLongitudeE6());
                        }
                    }
                    // Log.d(myapp.APP,"pair:" + pairs[i]);
                }
                //routeOverlays.add(new RouteOverlay(gp2,gp2, 3));
                mMapView01.getOverlays().add(new RouteOverlay(gp2, gp2, 3));
            } catch (NumberFormatException e) {
                //Log.e(myapp.APP, "Cannot draw route.", e);
            }
        }
        // mMapView01.getOverlays().addAll(routeOverlays); // use the default color
        mMapView01.setEnabled(true);
    }
    
}

class RoadRouteOverlay extends com.google.android.maps.Overlay {
    Road mRoad;
    ArrayList<GeoPoint> mPoints;

    public RoadRouteOverlay(Road road, MapView mv) {
            mRoad = road;
            if (road.mRoute.length > 0) {
                    mPoints = new ArrayList<GeoPoint>();
                    for (int i = 0; i < road.mRoute.length; i++) {
                            mPoints.add(new GeoPoint((int) (road.mRoute[i][1] * 1000000),
                                            (int) (road.mRoute[i][0] * 1000000)));
                    }
                    int moveToLat = (mPoints.get(0).getLatitudeE6() + (mPoints.get(
                                    mPoints.size() - 1).getLatitudeE6() - mPoints.get(0)
                                    .getLatitudeE6()) / 2);
                    int moveToLong = (mPoints.get(0).getLongitudeE6() + (mPoints.get(
                                    mPoints.size() - 1).getLongitudeE6() - mPoints.get(0)
                                    .getLongitudeE6()) / 2);
                    GeoPoint moveTo = new GeoPoint(moveToLat, moveToLong);

                    MapController mapController = mv.getController();
                    mapController.animateTo(moveTo);
                    mapController.setZoom(MainActivity.LOW_ZOOM);
            }
            Log.d("maps", "road.mRoute.length = "+road.mRoute.length);
            
    }

    @Override
    public boolean draw(Canvas canvas, MapView mv, boolean shadow, long when) {
        Log.d("maps", "mPoints.size() = "+mPoints.size());
            super.draw(canvas, mv, shadow);
            drawPath(mv, canvas);
            return true;
    }

    public void drawPath(MapView mv, Canvas canvas) {
            int x1 = -1, y1 = -1, x2 = -1, y2 = -1;
            Paint paint = new Paint();
            paint.setColor(Color.GREEN);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(3);
            for (int i = 0; i < mPoints.size(); i++) {
                    Point point = new Point();
                    mv.getProjection().toPixels(mPoints.get(i), point);
                    x2 = point.x;
                    y2 = point.y;
                    if (i > 0) {
                            canvas.drawLine(x1, y1, x2, y2, paint);
                    }
                    x1 = x2;
                    y1 = y2;
            }
    }
}

/*




*/